require File.dirname(__FILE__)+"/../../test_helper"
require "starframe/sprite/animation"

class StarFrame::Sprite::Animatable::Animation::Builder
  attr_accessor :target, :name, :options
  attr_accessor :animations, :max_frame
end
class StarFrame::Sprite::Animatable::Animation
  attr_accessor :target, :name, :options, :running
  attr_accessor :animations, :max_frame, :frame
end

class TestStarFrameSpriteAnimatable < Test::Unit::TestCase
  class AnimationObject
    attr_accessor :values
    def initialize
      @values = [0, 0, 0]
    end
  end
  
  def setup
    @animation = StarFrame::Sprite::Animatable::Animation::Builder.new(AnimationObject, :animation)
  end
  
  # Animation::Builder#initialize
  def test_initialize
    animation = StarFrame::Sprite::Animatable::Animation::Builder.new(AnimationObject, :animation)
    assert_equal AnimationObject, animation.target
    assert_equal :animation, animation.name
    assert_equal 0, animation.max_frame
    assert_equal 0, animation.animations.size
    assert_equal({:loop => true, :start => true}, animation.options)
  end
  def test_initialize_with_options_loop
    animation = StarFrame::Sprite::Animatable::Animation::Builder.new(AnimationObject, :animation, :loop => false)
    assert_equal AnimationObject, animation.target
    assert_equal :animation, animation.name
    assert_equal 0, animation.max_frame
    assert_equal 0, animation.animations.size
    assert_equal({:loop => false, :start => true}, animation.options)
  end
  def test_initialize_with_options_start
    animation = StarFrame::Sprite::Animatable::Animation::Builder.new(AnimationObject, :animation, :start => false)
    assert_equal AnimationObject, animation.target
    assert_equal :animation, animation.name
    assert_equal 0, animation.max_frame
    assert_equal 0, animation.animations.size
    assert_equal({:loop => true, :start => false}, animation.options)
  end
  def test_initialize_with_block
    animation = StarFrame::Sprite::Animatable::Animation::Builder.new(AnimationObject, :animation){ frames(1){} }
    assert_equal AnimationObject, animation.target
    assert_equal :animation, animation.name
    assert_equal 1, animation.max_frame
    assert_equal 1, animation.animations.size
  end
  
  # アサーションを定義
  def assert_builder_animations animation, max_frame, animations
    assert_equal max_frame, animation.max_frame, "max_frame"
    assert_equal animations.size, animation.animations.size, "animations.size"
    animations.each do |frame, methods|
      assert_equal methods.size, animation.animations[frame].size, "animations[#{frame}].size"
      methods.each_with_index do |method, index|
        assert_equal method[0], animation.animations[frame][index][0], "animations[#{frame}][#{index}] method name"
        assert_equal method[1], animation.animations[frame][index][1], "animations[#{frame}][#{index}] method args"
      end
    end
  end
  def assert_animations animation, max_frame, animations
    assert_equal max_frame, animation.max_frame, "max_frame"
    assert_equal animations.size, animation.animations.size, "animations.size"
    animations.each_with_index do |animation_pattern, animation_index|
      frame, methods = animation_pattern
      assert_equal frame, animation.animations[animation_index][0], "animations[#{animation_index}] frame"
      if methods
        assert_equal methods.size, animation.animations[animation_index][1].size, "animations[#{animation_index}].size"
        methods.each_with_index do |method, index|
          assert_equal method[0], animation.animations[animation_index][1][index][0], "animations[#{animation_index}][#{index}] method name"
          assert_equal method[1], animation.animations[animation_index][1][index][1], "animations[#{animation_index}][#{index}] method args"
        end
      else
        assert_nil animation.animations[animation_index][1], "animations[#{animation_index}] nil"
      end
    end
  end
  
  # Animation::Builder#frames
  def test_frames
    @animation.frames([1, 2]){}
    assert_builder_animations @animation, 2, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1]],
      1 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 2]],
    }
  end
  def test_frames_with_interval
    @animation.frames([1, 2], 2){}
    assert_builder_animations @animation, 4, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1]],
      2 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 2]],
    }
  end
  def test_frames_with_loop_size
    @animation.frames(2, 2){}
    assert_builder_animations @animation, 4, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0]],
      2 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1]],
    }
  end
  
  # Animation::Builder#frame
  def test_frame
    @animation.frame{}
    assert_builder_animations @animation, 1, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0]],
    }
  end
  def test_frame_with_interval
    @animation.frame(2){}
    assert_builder_animations @animation, 2, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0]],
    }
  end
  
  # Animation::Builder#wait
  def test_wait
    @animation.wait(2)
    assert_builder_animations @animation, 2, {}
  end
  
  # Animation::Builder#serial
  def test_serial
    @animation.serial do
      frames(2){}
      frames(3){}
    end
    assert_builder_animations @animation, 5, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0]],
      1 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1]],
      2 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 0]],
      3 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 1]],
      4 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 2]],
    }
  end
  
  # Animation::Builder#parallel
  def test_parallel
    @animation.parallel do
      frames(2){}
      frames(3){}
    end
    assert_builder_animations @animation, 3, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0],
            [:_starframe_sprite_animatable_animation__animation_animation_frames_2, 0]],
      1 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1],
            [:_starframe_sprite_animatable_animation__animation_animation_frames_2, 1]],
      2 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 2]],
    }
  end
  def test_parallel_and_serial
    @animation.parallel do
      serial do
        frames(2){}
        frames(3){}
      end
      frames(3){}
    end
    assert_builder_animations @animation, 5, {
      0 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0],
            [:_starframe_sprite_animatable_animation__animation_animation_frames_3, 0]],
      1 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 1],
            [:_starframe_sprite_animatable_animation__animation_animation_frames_3, 1]],
      2 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 0],
            [:_starframe_sprite_animatable_animation__animation_animation_frames_3, 2]],
      3 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 1]],
      4 => [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 2]],
    }
  end
  
  # 指定したオブジェクトに正しくメソッドが定義されているか
  def test_animation_object_method
    @animation.parallel do
      serial do
        frames(2){}
        frames(3){}
      end
      frames(3){}
    end
    animation_object = AnimationObject.new
    assert_respond_to animation_object, :_starframe_sprite_animatable_animation__animation_animation_frames_1
    assert_respond_to animation_object, :_starframe_sprite_animatable_animation__animation_animation_frames_2
    assert_respond_to animation_object, :_starframe_sprite_animatable_animation__animation_animation_frames_3
    assert !defined?  animation_object._starframe_sprite_animatable_animation__animation_animation_frames_4
  end
  
  # Animation::Builder#build
  def test_build
    @animation.serial do
      frames(1){}
      wait 1
      parallel do
        frames(1){}
        frames(1){}
      end
    end
    animation_object = AnimationObject.new
    prepared_animation = @animation.build(animation_object)
    assert_instance_of StarFrame::Sprite::Animatable::Animation, prepared_animation
    assert_animations prepared_animation, 2, [
      [0, [[:_starframe_sprite_animatable_animation__animation_animation_frames_1, 0]]],
      [2, [[:_starframe_sprite_animatable_animation__animation_animation_frames_2, 0],
           [:_starframe_sprite_animatable_animation__animation_animation_frames_3, 0]]],
      [-1],
    ]
    
    assert_equal animation_object, prepared_animation.target
    assert_equal -1, prepared_animation.frame
    assert_equal({:loop => true}, prepared_animation.options)
    assert_equal true, prepared_animation.running
  end
  
  def test_build_no_start
    @animation.options[:start] = false
    animation_object = AnimationObject.new
    prepared_animation = @animation.build(animation_object)
    
    assert_equal animation_object, prepared_animation.target
    assert_equal -1, prepared_animation.frame
    assert_equal({:loop => true}, prepared_animation.options)
    assert_equal false, prepared_animation.running
  end
  
  # Animation#update
  def test_prepare_update
    @animation.serial do
      frames(3) do |c|
        self.values[0] = (1+c)*1
      end
      wait 1
      parallel do
        frames(3) do |c|
          self.values[1] = (1+c)*2
        end
        frames(3) do |c|
          self.values[2] = (1+c)*3
        end
      end
    end
    animation_object = AnimationObject.new
    prepared_animation = @animation.build(animation_object)
    
    [
      [0, 1, 0, 0],
      [1, 2, 0, 0],
      [2, 3, 0, 0],
      [3, 3, 0, 0],
      [4, 3, 2, 3],
      [5, 3, 4, 6],
      [6, 3, 6, 9],
      [0, 1, 6, 9],
      [1, 2, 6, 9],
      [2, 3, 6, 9],
      [3, 3, 6, 9],
      [4, 3, 2, 3],
    ].each do |frame, *values|
      prepared_animation.update
      assert_equal frame, prepared_animation.frame
      assert_equal values, animation_object.values, "frame #{frame} values"
    end
  end
  def test_prepare_update_unloop
    @animation.options[:loop] = false
    @animation.serial do
      frames(3) do |c|
        self.values[0] = (1+c)*1
      end
      wait 1
      parallel do
        frames(3) do |c|
          self.values[1] = (1+c)*2
        end
        frames(3) do |c|
          self.values[2] = (1+c)*3
        end
      end
    end
    animation_object = AnimationObject.new
    prepared_animation = @animation.build(animation_object)
    
    [
      [0, 1, 0, 0],
      [1, 2, 0, 0],
      [2, 3, 0, 0],
      [3, 3, 0, 0],
      [4, 3, 2, 3],
      [5, 3, 4, 6],
      [6, 3, 6, 9],
      [-1, 3, 6, 9],
      [-1, 3, 6, 9],
    ].each do |frame, *values|
      prepared_animation.update
      assert_equal frame, prepared_animation.frame
      assert_equal values, animation_object.values, "frame #{frame} values"
    end
  end
  # Animation#start Animation#stop Animation#rewind Animation#restart
  def test_prepare_start_stop
    @animation.serial do
      frames(10) do |c|
        self.values[0] = c
      end
    end
    animation_object = AnimationObject.new
    prepared_animation = @animation.build(animation_object)
    
    prepared_animation.update
    prepared_animation.update
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.stop
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.start
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 2, prepared_animation.frame
    assert_equal 2, animation_object.values[0]
    
    prepared_animation.rewind
    assert_equal -1, prepared_animation.frame
    assert_equal 2, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 0, prepared_animation.frame
    assert_equal 0, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.stop
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.restart
    assert_equal -1, prepared_animation.frame
    assert_equal 1, animation_object.values[0]
    
    prepared_animation.update
    assert_equal 0, prepared_animation.frame
    assert_equal 0, animation_object.values[0]
  end
end
